﻿using DevExpress.ExpressApp;
using DevExpress.ExpressApp.DC;
using DevExpress.ExpressApp.DC.Internal;
using DevExpress.Utils;
using Microsoft.OData.Client;

namespace EasyXaf.ObjectSpaces.OData;

public class ODataTypeInfoSource : ReflectionTypeInfoSource, ITypeInfoSource, IEntityStore
{
    private readonly ITypesInfo _typesInfo;
    private readonly Dictionary<Type, object> _registeredTypeCache;
    private readonly Dictionary<Type, bool> _isKnownTypeCache;

    private HashSet<Type> _entityTypes;

    public IEnumerable<Type> RegisteredEntities => _registeredTypeCache.Keys;

    public ODataTypeInfoSource(ITypesInfo typesInfo, IList<Type> entityTypes)
        : base(typesInfo)
    {
        Guard.ArgumentNotNull(typesInfo, "typesInfo");

        _typesInfo = typesInfo;
        _registeredTypeCache = new Dictionary<Type, object>();
        _isKnownTypeCache = new Dictionary<Type, bool>();

        SetEntityTypes(entityTypes);
    }

    public ODataTypeInfoSource(ITypesInfo typesInfo)
        : this(typesInfo, null)
    {
    }

    public ODataTypeInfoSource()
        : this(XafTypesInfo.Instance)
    {
    }

    private static bool CalcIsDomainComponent(TypeInfo info)
    {
        return info.IsAttributeDefined<DomainComponentAttribute>(recursive: true);
    }

    private static XafMemberInfo GetAssociationMember(XafMemberInfo memberInfo)
    {
        var typeInfo = memberInfo.IsList ? memberInfo.ListElementTypeInfo : memberInfo.MemberTypeInfo;
        var memberInfos = typeInfo.OwnMembers
            .Where(m => m.IsPublic && m.IsProperty)
            .Where(m => !m.IsAttributeDefined<IgnoreClientPropertyAttribute>(true))
            .Where(m => typeof(ODataObject).IsAssignableFrom(m.IsList ? m.ListElementType : m.MemberType))
            .ToList();

        if (memberInfos.Any())
        {
            var associationName = memberInfo.FindAttribute<ODataAssociationAttribute>()?.Name;
            if (associationName == null)
            {
                if (memberInfos.Count > 1)
                {
                    throw new Exception($"在'{memberInfo.Owner.Type}'与'{typeInfo.Type}'之间，存在多个隐式的关系，请用ODataAssociationAttribute进行显式配置");
                }
            }
            else
            {
                memberInfos = memberInfos
                    .Where(m => m.FindAttribute<ODataAssociationAttribute>()?.Name == associationName)
                    .ToList();

                if (memberInfos.Count > 1)
                {
                    throw new Exception($"在'{memberInfo.Owner.Type}'与'{typeInfo.Type}'之间，存在多个关系名称为'{associationName}'的关系");
                }
            }

            return memberInfos.OfType<XafMemberInfo>().FirstOrDefault();
        }

        return null;
    }

    public void SetEntityTypes(IList<Type> entityTypes)
    {
        if (entityTypes != null)
        {
            _entityTypes = new HashSet<Type>();
            foreach (Type entityType in entityTypes)
            {
                _entityTypes.Add(entityType);
            }
        }
        else
        {
            _entityTypes = null;
        }
    }

    public override void InitMemberInfo(object member, XafMemberInfo memberInfo)
    {
        base.InitMemberInfo(member, memberInfo);

        var internalMemberInfo = GetInternalMemberInfo(member);
        var innerMemberInfo = new XafMemberInfoInternal(internalMemberInfo, memberInfo);
        memberInfo.SetInnerMemberInfo(innerMemberInfo);

        HandleKeyAttribute(memberInfo);

        memberInfo.IsDelayed = memberInfo.IsAttributeDefined<ODataDelayedAttribute>(false);
        if (memberInfo.IsPublic && memberInfo.IsProperty && !memberInfo.IsAttributeDefined<IgnoreClientPropertyAttribute>(true))
        {
            memberInfo.IsPersistent = !memberInfo.IsReadOnly;

            if (memberInfo.IsList && typeof(ODataObject).IsAssignableFrom(memberInfo.ListElementType))
            {
                memberInfo.IsPersistent = false;
                memberInfo.IsAssociation = true;
                memberInfo.IsAggregated = memberInfo.IsAttributeDefined<AggregatedAttribute>(false);
            }
            else if (memberInfo.IsPersistent && typeof(ODataObject).IsAssignableFrom(memberInfo.MemberType))
            {
                memberInfo.IsAssociation = true;
            }

            if (memberInfo.IsAssociation)
            {
                memberInfo.AssociatedMemberInfo = GetAssociationMember(memberInfo);
                memberInfo.AssociatedMemberName = memberInfo.AssociatedMemberInfo?.Name;
                memberInfo.AssociatedMemberOwner = memberInfo.AssociatedMemberInfo?.Owner.Type;
            }

            if (memberInfo.AssociatedMemberInfo != null)
            {
                if (memberInfo.IsList)
                {
                    memberInfo.IsManyToMany = memberInfo.AssociatedMemberInfo.IsList;
                }
                else
                {
                    memberInfo.IsReferenceToOwner = memberInfo.AssociatedMemberInfo.IsAttributeDefined<AggregatedAttribute>(false);
                }
            }
        }
    }

    public override void InitTypeInfo(TypeInfo info)
    {
        base.InitTypeInfo(info);

        info.IsVisible = CalcTypeVisibility(info);
        info.IsDomainComponent = CalcIsDomainComponent(info);
        info.IsPersistent = true;
    }

    public override bool TypeIsKnown(Type type)
    {
        bool value;

        if (_entityTypes != null)
        {
            value = _entityTypes.Contains(type);
        }
        else if (!_isKnownTypeCache.TryGetValue(type, out value))
        {
            value = type.IsClass && !type.ContainsGenericParameters && type.IsDefined(typeof(DomainComponentAttribute), inherit: false);
            _isKnownTypeCache.Add(type, value);
        }

        if (value)
        {
            value = typeof(ODataObject).IsAssignableFrom(type)
                && type.IsDefined(typeof(EntityTypeAttribute), inherit: true)
                && type.IsDefined(typeof(KeyAttribute), inherit: true);
        }

        return value;
    }

    public override bool RegisterNewMember(ITypeInfo owner, XafMemberInfo memberInfo)
    {
        if (!string.IsNullOrWhiteSpace(memberInfo.Expression) || !typeof(ICustomPropertyStore).IsAssignableFrom(owner.Type))
        {
            return base.RegisterNewMember(owner, memberInfo);
        }

        memberInfo.SetInnerMemberInfo(new CustomWritableMemberInfoInternal(memberInfo));
        AddToTypesWithCustomMembers(owner.Type);

        return true;
    }

    public bool CanRegister(Type type)
    {
        if (TypeIsKnown(type))
        {
            return !_registeredTypeCache.ContainsKey(type);
        }

        return false;
    }

    public void RegisterEntity(Type type)
    {
        if (TypeIsKnown(type) && !_registeredTypeCache.ContainsKey(type))
        {
            _registeredTypeCache.Add(type, null);

            var typeInfo = (TypeInfo)_typesInfo.FindTypeInfo(type);
            RegisterUsedEnumerations(typeInfo);

            typeInfo.Source = this;
            typeInfo.Refresh(refreshMembers: true);
        }
    }

    public override void Reset()
    {
        base.Reset();
        _registeredTypeCache.Clear();
    }
}
